<template>
  <div class="draggableX" id="draggableXDom" @dragenter="inFn">
    <!-- 内容插槽 -->
    <span id="dranggableFatherId" style="display: none">{{ props.groupValue }}</span>

    <slot name="draggBox" :data="props.list">
      <div class="draggableItem" :class="props.itemClass"
           v-for="item in props.list" :key="item[props.idValue]">
        <slot name="dataId" :data="item">
          <span :id="props.dataId" style="display: none">{{ item[props.idValue] }}</span>
        </slot>
        <!-- 列表插槽 -->
        <span class="dragItem">
          <slot name="item" :data="item"></slot>
        </span>
      </div>
    </slot>

    <!-- 更多插槽，为了上传文件组件的添加按钮 -->
    <slot name="draggMore" />
  </div>
</template>

<script setup>
import {ref, onMounted, nextTick} from 'vue'
import Sortable from './js/sortablejs-1.15.1/package/modular/sortable.complete.esm.js'

const props = defineProps({
  // 遍历的数据
  list: {
    type: Object,
    default: []
  },
  // draggableItem的类名
  itemClass: {
    type: [String, Object],
    default: ''
  },

  // 数据的唯一id。用于识别当前操作item
  idValue: {
    type: String,
    default: 'id'
  },

  // 数据id的dom类名
  dataId: {
    type: String,
    default: 'draggableItemId'
  },

  // 组的唯一id值，用于识别当前组的拖拽区域
  groupValue: {
    type: [String, Number],
    default: "fatherValue"
  },

  // 拖拽元素的ID
  draggId: {
    type: String,
    default: undefined
  },


  // 一个网页存在多个分组时设置
  // or { name: "...", pull: [true, false, 'clone', array], put: [true, false, array] }
  group: {
    type: [String, Object, Number],
    default: "allDraggableX"
  },
  // 是否允许列内部排序，如果为false当有多个排序组时,多个组之间可以拖拽，本身不能拖拽
  sort: {
    type: Boolean,
    default: true
  },
  // 按下鼠标后多久可以拖拽 1000表示1秒
  delay: {
    type: [Number, String],
    default: 0
  },
  // 如果为false按下鼠标不动才计算延时，移动则无效
  delayOnTouchOnly: {
    type: Boolean,
    default: false
  },
  // 当按下鼠标移动了n个px时会取消延迟delay事件，即可超过了这个范围无法拖动元素了。
  // 在取消延迟拖动事件之前，点应该移动多少像素
  touchStartThreshold: {
    type: [Number, String],
    default: 0
  },
  // 启用禁用拖拽
  disabled: {
    type: Boolean,
    default: false
  },
  // Store
  store: null,

  // 动画效果
  animation: {
    type: [String, Number],
    default: 500
  },
  // Easing 动画 默认null. See https://easings.net/ for examples.
  easing: {
    type: String,
    default: 'cubic-bezier(1, 0, 0, 1)'
  },
  // 句柄，点击指定class样式的对象才能拖拽元素，如：".myHandle"
  handle: {
    type: String,
    default: '.dragItem'
  },
  // 忽略class为ignore-elements的元素不能拖动，如：".ignoreX"。或者通过函数来实现过滤不允许拖动的对象
  filter: {
    type: [String, Function],
    default: undefined
  },
  // 触发filter时调用`event.preventDefault()`
  preventOnFilter: {
    type: Boolean,
    default: true
  },
  // 指定那些元素类名可以被拖拽。如：".item"
  draggable: {
    type: String,
    default: undefined
  },


  // 指定获取拖动后排序的data属性
  dataIdAttr: {
    type: String,
    default: ""
  },


  // 停靠位置的自定义样式。按住时移到拖拽区时添加类名，不需要小点。如：'ghostX'
  ghostClass: {
    type: String,
    default: undefined
  },
  // 选中元素的自定义样式。按住的类名，如："clickX"
  chosenClass: {
    type: String,
    default: undefined
  },
  // 拖拽时的自定义样式。如："draggingX"
  dragClass: {
    type: String,
    default: undefined
  },


  // 交互区大小，A元素到B元素内多深的距离触发替换位置
  swapThreshold: {
    type: [String, Number],
    default: 1000
  },
  // 如果设置为 true，将始终使用反向交换区域
  invertSwap: {
    type: Boolean,
    default: false
  },
  //拖拽方向(默认会自动判断方向)
  direction: {
    type: String,
    default: 'horizontal'
  },


  // 忽略HTML5原生拖拽行为，禁止后不会除非setData
  forceFallback: {
    type: Boolean,
    default: false
  },


  // 拖拽时被克隆元素的样式名称，如："cloneX"
  fallbackClass: {
    type: String,
    default: "cloneX"
  },
  // 将克隆的 DOM 元素追加到文档的主体中
  fallbackOnBody: {
    type: Boolean,
    default: false
  },
  // 优先级比fallbackOnBody高
  toBody: {
    type: Boolean,
    default: false
  },
  // 以像素为单位指定鼠标在被视为拖动之前应该移动多远
  fallbackTolerance: {
    type: [Number, String],
    default: 8
  },


  dragoverBubble: {
    type: Boolean,
    default: false
  },
  // 删除不显示的克隆元素，而不仅仅是隐藏它
  removeCloneOnHide: {
    type: Boolean,
    default: true
  },
  // 距离鼠标必须从空排序，以插入拖动元素到它
  emptyInsertThreshold: {
    type: [String, Number],
    default: 100
  },

  // 设置参数的函数。推荐使用拖拽前startBefore方法
  setDataFn: {
    type: Function,
    default: undefined
  },

  // 拖拽前回调函数，返回{}对象，会转为JSON值给setData函数
  startBefore: {
    type: Function,
    default: undefined
  },

})

const emit = defineEmits(['choose', 'unChoose', 'start', 'end', 'add',
  'update', 'sort', 'remove', 'filter', 'move', 'clone', 'change', 'endData'])

const sortable = ref({})

const options = ref({})

/* 回调方法 */
// 获取当前操作的item值
const getItemValue = (itemDom) => {
  if (!itemDom) {
    return undefined
  }
  let value = undefined
  // 获取key值
  let keyDom = itemDom.querySelector(`#${props.dataId}`)
  if (keyDom) {
    // 获取到key值
    value = keyDom.innerHTML
  }
  return value
}

// 获取当前操作item
const getItem = (itemDom) => {
  let item = undefined
  // 获取key值
  let itemValue = getItemValue(itemDom)
  if (itemValue) {
    item = props.list.find(row => row[props.idValue] == itemValue)
  }
  return item
}

// 获取操作区域值
const getGroupValue = fromOrTo => {
  let value = undefined
  // 获取key值
  let keyDom = fromOrTo.querySelector("#dranggableFatherId")
  if (keyDom) {
    // 获取到key值
    value = keyDom.innerHTML
  }
  return value
}

// 发送共同事件。
/*
* eventName：事件名称。
* evt：封装的js的回调evt对象
* itemDom：当前元素对象，用户获取内置元素值的。
* eventObj：html的拖拽事件对象
* otherData：传递的其他参数
* */
const toEmit = (eventName, evt, itemDom = undefined, otherData = {}) => {
  if (itemDom != undefined) {
    evt.item = itemDom
  }
  // 当前对象
  let nowItem = getItem(evt.item)
  let itemValue = getItemValue(evt.item)
  // 从哪个区域
  let formGroup = getGroupValue(evt.from)
  // 去哪个区域
  let toGroup = getGroupValue(evt.to)

  // html的拖拽事件对象
  let htmlEvent = evt.originalEvent || {}

  let data = {
    // 封装的js的回调evt对象
    event: evt,
    // 如果被加入区域的props.list并没有这个条数据的draggableItemId，因此item返回undefined
    item: nowItem,
    // 拖拽开始的区域标识
    from: formGroup,
    // 拖拽结束的区域标识
    to: toGroup,
    // 当前拖拽元素的标致
    itemValue: itemValue,
    // 当前拖拽元素绑定的列表
    list: props.list,
    // html的拖拽事件对象
    htmlEvent: htmlEvent
  }
  for (let key in otherData) {
    data.key = otherData
  }
  emit(eventName, data)

  // 拖拽后回调函数
  let toAfter = ['end']
  if (toAfter.includes(eventName)) {
    let afterSetData = {}
    if (htmlEvent.dataTransfer) {
      let obj = htmlEvent.dataTransfer
      obj.types.forEach(key => {
        let value = obj.getData(key);
        try {
          afterSetData[key] = JSON.parse(value)
        } catch (err) {
          afterSetData[key] = value
        }
      })
    }
    emit("endData", afterSetData)
  }
}

// 点击选中元素事件
const chooseFn = (evt, two) => {
  toEmit('choose', evt)
}

// 取消选中事件
const unChooseFn = evt => {
  toEmit('unChoose', evt)
}

// 开始拖拽
const startFn = evt => {
  toEmit('start', evt)
}

// 结束拖拽
const endFn = evt => {
  toEmit('end', evt)
}

// 拖拽元素被加入其他区域
const addFn = evt => {
  toEmit('add', evt)
}

// 排序发生改变时的事件
const updateFn = evt => {
  toEmit('update', evt)
}

// 通过对列表的任何更改(添加/更新/删除)调用
const sortFn = evt => {
  toEmit("sort", evt)
}

// 从列表中移出时
const removeFn = evt => {
  toEmit('remove', evt)
}

// 试图拖动筛选元素
const filterFn = (evt) => {
  toEmit('filter', evt)
}

// 在列表中或列表之间移动项时，将引发
const moveFn = (evt, event) => {
  toEmit("move", evt, evt.dragged, {
    event: event
  })
}

// 在创建元素的克隆时调用
const cloneFn = evt => {
  toEmit("clone", evt)
}

// 当拖动元素改变位置时调用
const changeFn = evt => {
  toEmit('change', evt)
}

// HTML的拖拽开始时事件
const setData = async (dataTransfer, dragEl) => {
  let defaultData = {
    list: props.list,
    groupValue: props.groupValue,
  }
  if (typeof props.setDataFn == 'function') {
    props.setDataFn(dataTransfer, dragEl, defaultData)
  } else {

    if (typeof props.startBefore == 'function') {
      let result = await props.startBefore(dataTransfer, dragEl, defaultData)
      for (let key in result) {
        defaultData[key] = result[key]
      }
    }
    for (let key in defaultData) {
      dataTransfer.setData(key, JSON.stringify(defaultData[key]))
    }
  }
}

// 初始化
const domId = ref("draggableXDom")
onMounted(() => {
  nextTick(() => {
    let thisDom = document.getElementById(domId.value)
    if (props.draggId) {
      domId.value = props.draggId
      if (thisDom != null && thisDom != undefined) {
        thisDom.setAttribute("id", props.draggId)
      }
    } else {
      // 自动生成，则判断没有这个dom时
      let tempId = `draggableXDom_${Date.now()}`
      while (true) {
        let haveDom = document.getElementById(tempId)
        if (haveDom) {
          // 已经存在则换一个id
          tempId = `draggableXDom_${Date.now()}`
        } else {
          break;
        }
      }
      domId.value = tempId
      if (thisDom != null && thisDom != undefined) {
        thisDom.setAttribute("id", tempId)
      }
    }

    options.value = {
      group: typeof props.group == 'number' ? String(props.group) : props.group,
      sort: props.sort,
      delay: parseFloat(props.delay),
      delayOnTouchOnly: props.delayOnTouchOnly,
      touchStartThreshold: parseFloat(props.touchStartThreshold),
      disabled: props.disabled,
      store: props.store,
      animation: parseFloat(props.animation),
      easing: props.easing,
      handle: props.handle,
      filter: props.filter,
      preventOnFilter: props.preventOnFilter,
      draggable: props.draggable,
      dataIdAttr: props.dataIdAttr,
      ghostClass: props.ghostClass,
      chosenClass: props.chosenClass,
      dragClass: props.dragClass,
      swapThreshold: parseFloat(props.swapThreshold),
      invertSwap: props.invertSwap,
      direction: props.direction,
      forceFallback: props.forceFallback,
      fallbackClass: props.fallbackClass,
      fallbackOnBody: props.toBody != undefined ? props.toBody : props.fallbackOnBody,
      fallbackTolerance: parseFloat(props.fallbackTolerance),
      dragoverBubble: props.dragoverBubble,
      removeCloneOnHide: props.removeCloneOnHide,
      emptyInsertThreshold: parseFloat(props.emptyInsertThreshold),

      // 拖拽时HTML事件调用
      setData: setData,

      /* 回调事件 */
      onChoose: chooseFn,
      onUnchoose: unChooseFn,
      onStart: startFn,
      onEnd: endFn,
      onAdd: addFn,
      onUpdate: updateFn,
      onSort: sortFn,
      onRemove: removeFn,
      onFilter: filterFn,
      onMove: moveFn,
      onClone: cloneFn,
      onChange: changeFn,
    }


    if (props.draggable == undefined || props.draggable == null || props.draggable.length <= 0) {
      delete options.value.draggable
    }
    if (thisDom) {
      sortable.value = Sortable.create(thisDom, options.value)
    }
  })
})


/* 公开方法 */
// 重新设置参数
const setOptions = () => {

}


defineExpose({
  setOptions,
  // 获取当前组件的拖动id
  domId
})
</script>

<style scoped lang='scss'>
.draggableX {
  height: auto;
  width: auto;
  //background-color: yellowgreen;
}

.draggableItem {
  height: auto;
  width: 100%;
  display: inline-block;
}

</style>